package com.cbs.bluetooth.device.qunsuo;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import app.akexorcist.bluetotohspp.library.BluetoothSPP;
import app.akexorcist.bluetotohspp.library.BluetoothState;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by broche on 10/9/15.
 */
public class Printer {
    //    private static final String TAG = Printer.class.getName();
    private Activity activity = null;
    private Fragment fragment = null;
    private android.support.v4.app.Fragment fragmentv4 = null;
    private String address = "";
    private List<PrintContent> contents = new ArrayList<>();
    private PrintListener listener = null;
    private BluetoothSPP bluetooth = null;
    private PrintState state = PrintState.Waiting;

    private MyConnectionListener connectionListener = new MyConnectionListener(this);
    private StateHandler stateHandler = new StateHandler(this);
    private FailureStateHandler failureStateHandler = new FailureStateHandler(this);
    private boolean isAndroid = false;

    public Printer(Activity activity, PrintListener listener) {
        this.activity = activity;
        this.listener = listener;
    }

    public Printer(Fragment fragment, PrintListener listener) {
        this.fragment = fragment;
        this.listener = listener;
    }

    public Printer(android.support.v4.app.Fragment fragment, PrintListener listener) {
        this.fragmentv4 = fragment;
        this.listener = listener;
    }

    public boolean isAndroid() {
        return isAndroid;
    }

    public void setAndroid(boolean android) {
        isAndroid = android;
    }

    public PrintState getState() {
        return state;
    }

    public void setState(PrintState state) {
//        Exception exception = new Exception("state: " + state);
//        L.e(TAG, "", exception);
        this.state = state;
    }

    public void print(String content, String address) {
        List<PrintContent> list = new ArrayList<>();
        PrintContent printContent = new PrintContent(PrintContentType.Text, content, null);
        list.add(printContent);
        print(list, address);
    }

    public void print(List<PrintContent> contents, String address) {
        if (getState() == PrintState.Waiting) {
            if (address == null || address.equals("")) {
                failureStateHandler.obtainMessage(PrintFailureState.PrinterNotFound.value()).sendToTarget();
                return;
            }
            if (contents == null || contents.size() <= 0) {
                failureStateHandler.obtainMessage(PrintFailureState.NoContent.value()).sendToTarget();
                return;
            }
            this.address = address;
            this.contents = contents;
            setState(PrintState.Initing);
            stateHandler.obtainMessage(PrintState.Initing.value()).sendToTarget();
            init();
        } else {
            failureStateHandler.obtainMessage(PrintFailureState.PrinterIsConnecting.value()).sendToTarget();
        }
    }

    private static class ActivityResultHandler extends Handler {
        private WeakReference<Printer> reference;

        public ActivityResultHandler(Printer printer) {
            reference = new WeakReference<>(printer);
        }

        @Override
        public void handleMessage(Message msg) {
            Printer printer = reference.get();
            if (printer != null) {
                printer.setupService();
                printer.setState(PrintState.Connecting);
                printer.stateHandler.obtainMessage(PrintState.Connecting.value()).sendToTarget();
                printer.connect();
            }
        }
    }

    private Handler activityResultHandler = new ActivityResultHandler(this);

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == BluetoothState.REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_OK) {
                // delay for some device not respond
                activityResultHandler.sendEmptyMessageDelayed(0, 3000);
            } else {
                failureStateHandler.obtainMessage(PrintFailureState.BluetoothNotEnabled.value()).sendToTarget();
                setState(PrintState.Waiting);
//                // TODO need to do this?
//                stateHandler.obtainMessage(PrintState.Waiting.value()).sendToTarget();
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void init() {
        if (bluetooth != null) {
            bluetooth.cancelDiscovery();
            bluetooth.disconnect();
            bluetooth.stopService();
            bluetooth = null;
        }
        if (this.activity != null) {
            bluetooth = new BluetoothSPP(this.activity);
        } else if (this.fragment != null) {
            bluetooth = new BluetoothSPP(this.fragment.getActivity());
        } else if (this.fragmentv4 != null) {
            bluetooth = new BluetoothSPP(this.fragmentv4.getActivity());
        }
        bluetooth.setBluetoothConnectionListener(connectionListener);

        if (!bluetooth.isBluetoothAvailable()) {
            setState(PrintState.Waiting);
//            // TODO need to do this?
//            stateHandler.obtainMessage(PrintState.Waiting.value()).sendToTarget();
            failureStateHandler.obtainMessage(PrintFailureState.BluetoothNotAvailable.value()).sendToTarget();
            return;
        }
        if (!bluetooth.isBluetoothEnabled()) {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            if (this.activity != null) {
                activity.startActivityForResult(intent, BluetoothState.REQUEST_ENABLE_BT);
            } else if (this.fragment != null) {
                fragment.startActivityForResult(intent, BluetoothState.REQUEST_ENABLE_BT);
            } else {
                fragmentv4.startActivityForResult(intent, BluetoothState.REQUEST_ENABLE_BT);
            }
        } else {
            setupService();
            setState(PrintState.Connecting);
            stateHandler.obtainMessage(PrintState.Connecting.value()).sendToTarget();
            connect();
        }
    }

    private void setupService() {
        if (!bluetooth.isServiceAvailable()) {
            bluetooth.setupService();
            bluetooth.startService(isAndroid);
        }
    }

    private void connect() {
        if (getState() == PrintState.Canceling) {
            return;
        }
        bluetooth.cancelDiscovery();
        bluetooth.connect(address);
    }

    private static class PrintingHandler extends Handler {
        private WeakReference<Printer> reference;

        public PrintingHandler(Printer printer) {
            reference = new WeakReference<Printer>(printer);
        }

        @Override
        public void handleMessage(Message msg) {
            Printer printer = reference.get();
            if (printer != null) {
                if (printer.getState() == PrintState.Canceling) {
                    return;
                }
                printer.setState(PrintState.Finishing);
                printer.stateHandler.obtainMessage(PrintState.Finishing.value()).sendToTarget();
                printer.finishing();
            }
        }
    }

    private Handler printingHandler = new PrintingHandler(this);


    private static class PrintFinishingHandler extends Handler {
        private WeakReference<Printer> reference;

        public PrintFinishingHandler(Printer printer) {
            reference = new WeakReference<>(printer);
        }

        @Override
        public void handleMessage(Message msg) {
            Printer printer = reference.get();
            if (printer != null) {
                if (printer.getState() == PrintState.Canceling) {
                    return;
                }
                printer.bluetooth.send("".getBytes(), false);
                printer.bluetooth.stopService();
                printer.printingHandler.sendEmptyMessageDelayed(0, 1000);
            }
        }
    }

    private Handler printFinishingHandler = new PrintFinishingHandler(this);

    private void printing() {
        List<byte[]> byteList = new ArrayList<>();
        int al = 0;
        for (PrintContent content : this.contents) {
            if (content.getType() == PrintContentType.Text) {
                byte[] bytes;
                try {
                    bytes = content.getContent().getBytes("GBK");
                } catch (UnsupportedEncodingException e) {
                    bytes = content.getContent().getBytes();
                }
                al += bytes.length;
                byteList.add(bytes);
            }
            if (content.getType() == PrintContentType.Img || content.getType() == PrintContentType.QrCode) {
                byte[] imgs = bitmap2Bytes(content.getBitmap());
                int l = imgs.length / 30;
                l += imgs.length % 30 == 0 ? 0 : 1;
                for (int i = 0; i < l; i++) {
                    byte[] d = new byte[38];
                    for (int j = 0; j < 30; j++) {
                        if (i * 30 + j < imgs.length) {
                            d[j + 8] = imgs[i * 30 + j];
                        } else {
                            d[j + 8] = 0;
                        }
                    }
                    al += d.length;
                    byteList.add(d);
                }
            }
            byte[] bytes;
            try {
                bytes = "\n".getBytes("GBK");
            } catch (UnsupportedEncodingException e) {
                bytes = "\n".getBytes();
            }
            al += bytes.length;
            byteList.add(bytes);
        }
        byte[] all = new byte[al];
        int length = 0;
        for(byte[] bytes:byteList){
            for(int i=0;i<bytes.length;i++){
                all[length]=bytes[i];
                length++;
            }
        }

        bluetooth.send(all, true);

        printFinishingHandler.sendEmptyMessageDelayed(0, 1000);
    }

    private static class FinishingHandler extends Handler {
        private WeakReference<Printer> reference;

        public FinishingHandler(Printer printer) {
            reference = new WeakReference<>(printer);
        }

        @Override
        public void handleMessage(Message msg) {
            Printer printer = reference.get();
            if (printer != null) {
                if (printer.getState() == PrintState.Canceling) {
                    return;
                }
                printer.setState(PrintState.Waiting);
//                // TODO need to do this?
//                printer.stateHandler.obtainMessage(PrintState.Waiting.value()).sendToTarget();
                printer.listener.onSuccess();
            }
        }
    }

    private Handler finishingHandler = new FinishingHandler(this);

    private void finishing() {
        finishingHandler.sendEmptyMessageDelayed(0, 2000);
    }

    private static class CancelHandler extends Handler {
        private WeakReference<Printer> reference;

        public CancelHandler(Printer printer) {
            reference = new WeakReference<>(printer);
        }

        @Override
        public void handleMessage(Message msg) {
            Printer printer = reference.get();
            if (printer != null) {
                printer.setState(PrintState.Waiting);
//                    // TODO need to do this?
//                    printer.stateHandler.obtainMessage(PrintState.Waiting.value()).sendToTarget();
                printer.listener.onFailure(PrintFailureState.UserCanceled);
            }
        }
    }

    private Handler cancelHandler = new CancelHandler(this);

    private class CancelFinishingHandler extends Handler {
        private WeakReference<Printer> reference;

        public CancelFinishingHandler(Printer printer) {
            reference = new WeakReference<>(printer);
        }

        @Override
        public void handleMessage(Message msg) {
            Printer printer = reference.get();
            if (printer != null) {
                if (printer.bluetooth != null) {
                    printer.bluetooth.stopService();
                }
                printer.activityResultHandler.removeMessages(0);
                printer.retryHandler.removeMessages(0);
                printer.printingHandler.removeMessages(0);
                printer.printFinishingHandler.removeMessages(0);
                printer.finishingHandler.removeMessages(0);
                printer.cancelHandler.sendEmptyMessageDelayed(0, 2000);
            }
        }
    }

    private Handler cancelFinishingHandler = new CancelFinishingHandler(this);

    public void cancel() {
        if (getState() == PrintState.Initing || getState() == PrintState.Connecting || getState() == PrintState.Connecting2 || getState() == PrintState.Connecting3) {
            setState(PrintState.Canceling);
            listener.onStateChanged(getState());
            cancelFinishingHandler.sendEmptyMessageDelayed(0, 2000);
        } else {
            // TODO do nothing?
//            failureStateHandler.obtainMessage(PrintFailureState.PrinterIsConnecting.value()).sendToTarget();
        }
    }

    private static class RetryHandler extends Handler {
        private WeakReference<Printer> reference;

        public RetryHandler(Printer printer) {
            this.reference = new WeakReference<>(printer);
        }

        @Override
        public void handleMessage(Message msg) {
            Printer printer = reference.get();
            if (printer != null) {
                if (printer.getState() == PrintState.Canceling) {
                    return;
                }
                printer.connect();
            }
        }
    }

    private RetryHandler retryHandler = new RetryHandler(this);

    private class MyConnectionListener implements BluetoothSPP.BluetoothConnectionListener {
        private WeakReference<Printer> reference;

        public MyConnectionListener(Printer printer) {
            reference = new WeakReference<>(printer);
        }

        public void onDeviceConnected(String name, String address) {
            Printer printer = reference.get();
            if (printer != null) {
                if (printer.getState() == PrintState.Canceling) {
                    return;
                }
                printer.setState(PrintState.Printing);
                printer.stateHandler.obtainMessage(PrintState.Printing.value()).sendToTarget();
                printer.printing();
            }
        }

        public void onDeviceDisconnected() {
            Printer printer = reference.get();

            if (printer != null) {
                if (printer.getState() == PrintState.Canceling) {
                    return;
                }
                printer.setState(PrintState.Waiting);
//            // TODO need to do this?
//            printer.stateHandler.obtainMessage(PrintState.Waiting.value()).sendToTarget();
            }
        }

        public void onDeviceConnectionFailed() {
            Printer printer = reference.get();
            if (printer != null) {
                if (printer.getState() == PrintState.Canceling) {
                    return;
                }
                if (printer.getState() == PrintState.Connecting) {
                    printer.setState(PrintState.Connecting2);
                    printer.stateHandler.obtainMessage(PrintState.Connecting2.value()).sendToTarget();
                    printer.retryHandler.sendEmptyMessageDelayed(0, 3000);
                } else if (printer.getState() == PrintState.Connecting2) {
                    printer.setState(PrintState.Connecting3);
                    printer.stateHandler.obtainMessage(PrintState.Connecting3.value()).sendToTarget();
                    printer.retryHandler.sendEmptyMessageDelayed(0, 3000);
                } else if (printer.getState() == PrintState.Canceling) {
                    // do nothing
                } else {
                    printer.setState(PrintState.Waiting);
//                // TODO need to do this?
//                printer.stateHandler.obtainMessage(PrintState.Waiting.value()).sendToTarget();
                    printer.failureStateHandler.obtainMessage(PrintFailureState.PrinterNotFound.value()).sendToTarget();
                }
            }
        }
    }

    private static class StateHandler extends Handler {
        private WeakReference<Printer> reference;

        public StateHandler(Printer printer) {
            this.reference = new WeakReference<>(printer);
        }

        @Override
        public void handleMessage(Message msg) {
            Printer printer = reference.get();
            if (printer != null) {
                PrintState printState = PrintState.valueOf(msg.what);
                printer.listener.onStateChanged(printState);
            }
        }
    }

    private static class FailureStateHandler extends Handler {
        private WeakReference<Printer> reference;

        public FailureStateHandler(Printer printer) {
            reference = new WeakReference<>(printer);
        }

        @Override
        public void handleMessage(Message msg) {
            Printer printer = reference.get();
            if (printer != null) {
                PrintFailureState failureState = PrintFailureState.valueOf(msg.what);
                printer.listener.onFailure(failureState);
            }
        }
    }

    // Bitmap → byte[]
    public static byte[] bitmap2Bytes(Bitmap bm) {
        byte[] bytes = new byte[0];
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
            baos.flush();
            bytes = baos.toByteArray();
            bm.recycle();
            baos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }
}
